#include "shnetdebugger.h"
#include <QUdpSocket>
#include "qtquick2applicationviewer.h"
#include <QQuickItem>
#include "sh/url.h"

ShNetDebugger::ShNetDebugger(QObject *parent) :
    QObject(parent), m_inPackets(0), m_outPackets(0), m_inBytes(0), m_outBytes(0)
{
    m_udp = new QUdpSocket(this);
    connect(m_udp, &QUdpSocket::readyRead,
            this, &ShNetDebugger::processDatagrams);

    m_qml = new QtQuick2ApplicationViewer;
    m_qml->setMainQmlFile(QStringLiteral("qml/sh-debugger/main.qml"));
    m_qml->showExpanded();

    connect(m_qml->rootObject()->findChild<QObject *>("joinButton"), SIGNAL(clicked()),
            this, SLOT(onJoinClicked()));
    connect(m_qml->rootObject(), SIGNAL(setProperty(QString)),
            this, SLOT(setProperty(QString)));
    connect(m_qml->rootObject(), SIGNAL(execFunction(QString)),
            this, SLOT(execFunction(QString)));
    connect(m_qml->rootObject(), SIGNAL(propertyChange(QString)),
            this, SLOT(propertyChange(QString)));
}

void ShNetDebugger::processDatagrams()
{
    while (m_udp->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(m_udp->pendingDatagramSize());
        m_udp->readDatagram(datagram.data(), datagram.size());

        QDataStream in(&datagram, QIODevice::ReadOnly);
        in.setVersion(QDataStream::Qt_5_0);

        // check version
        quint8 protocolVersion;
        in >> protocolVersion;
        if (protocolVersion != 1) {
            qWarning() << "recieved unsupported frame with version:" << protocolVersion;
            return;
        }

        // ignore our frames
        QString sender_name;
        in >> sender_name;
        if (sender_name == m_nodeName)
            return;

        m_inPackets++;
        m_inBytes += datagram.size();
        m_qml->rootObject()->setProperty("inPackets", m_inPackets);
        m_qml->rootObject()->setProperty("inBytes", m_inBytes);

        // process frame
        quint8 frame_type;
        in >> frame_type;
        FRAME_TYPE frameType = (FRAME_TYPE)frame_type;

        Url url;
        in >> url;

        QVariant img;
        QVariant message;
        QVariant color = "#77ccff";

        if (frameType == PROPERY_CHANGE_NOTIFY || frameType == PROPERTY_SET) {
            img = frameType == PROPERY_CHANGE_NOTIFY ? "img/notify.png" : "img/set.png";
            message = QString("%1 [%2]").arg(url.toString())
                    .arg(url.argumentData().typeName());
        } else if (frameType == NETWORK_SIGNAL) {
            img = "img/signal.png";
            QVariantList parameters;
            in >> parameters;
            QString messageStr = url.toString();
            QString parameterStr("(%1)%2");
            foreach (QVariant p, parameters)
                messageStr += parameterStr.arg(p.typeName()).arg(p.toString());
            message = messageStr;
        } else if (frameType == FUNCTION_CALL) {
            img = "img/call.png";
            message = url.toString();
        } else if (frame_type == FUNCTION_CALL_RESULT) {
            img = "img/return.png";
            Url ret;
            in >> ret;
            message = QString("to %1 :: %2").arg(url.toString()).arg(ret.toString());
        }

        QMetaObject::invokeMethod(m_qml->rootObject()->findChild<QObject *>("packets"),
                                  "addItem",
                                  Q_ARG(QVariant, img),
                                  Q_ARG(QVariant, message),
                                  Q_ARG(QVariant, color));
    }
}

void ShNetDebugger::onJoinClicked()
{
    QHostAddress interface(m_qml->rootObject()->property("interfaceAddress").toString());
    QHostAddress group(m_qml->rootObject()->property("multicastGroup").toString());
    quint16 port = m_qml->rootObject()->property("port").toUInt();
    QString nodeName = m_qml->rootObject()->property("nodeName").toString();
    qDebug() << interface << group << port;
    bool ok = join(interface, group, port, nodeName);
    m_qml->rootObject()->setProperty("status", ok ? "Joined" : "Error, see log");
}

void ShNetDebugger::setProperty(const QString &url)
{
    sh_net_startmsg();
    out << (quint8)PROPERTY_SET;
    Url u(url);
    out << u;
    sh_net_sendmsg();
    outStatistic(datagram.size());
    QMetaObject::invokeMethod(m_qml->rootObject()->findChild<QObject *>("packets"),
                              "addItem",
                              Q_ARG(QVariant, "img/set.png"),
                              Q_ARG(QVariant, u.toString()),
                              Q_ARG(QVariant, "yellow"));
}

void ShNetDebugger::execFunction(const QString &url)
{
    sh_net_startmsg();
    out << (quint8)FUNCTION_CALL;
    Url u(url);
    out << u;
    sh_net_sendmsg();
    QMetaObject::invokeMethod(m_qml->rootObject()->findChild<QObject *>("packets"),
                              "addItem",
                              Q_ARG(QVariant, "img/call.png"),
                              Q_ARG(QVariant, u.toString()),
                              Q_ARG(QVariant, "yellow"));
}

void ShNetDebugger::propertyChange(const QString &url)
{
    sh_net_startmsg();
    out << (quint8)PROPERY_CHANGE_NOTIFY;
    Url u(url);
    out << u;
    sh_net_sendmsg();
    QMetaObject::invokeMethod(m_qml->rootObject()->findChild<QObject *>("packets"),
                              "addItem",
                              Q_ARG(QVariant, "img/notify.png"),
                              Q_ARG(QVariant, u.toString()),
                              Q_ARG(QVariant, "yellow"));
}

bool ShNetDebugger::join(const QHostAddress &interface,
                         const QHostAddress &multicastGroup,
                         quint16 port,
                         const QString &nodeName)
{
    if (m_udp->state() == QUdpSocket::BoundState) {
        m_udp->leaveMulticastGroup(m_groupAddress);
        m_udp->abort();
    }
    if (!m_udp->bind(interface, port,
                     QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) {
        qCritical() << "bind failed";
        return false;
    }

    if (m_udp->state() != QUdpSocket::BoundState) {
        qCritical() << "socket is not in bound state";
        return false;
    }

    if (!m_udp->joinMulticastGroup(multicastGroup)) {
        qWarning() << "can't join multicast group, working broadcast";
        m_groupAddress = QHostAddress("255.255.255.255");
    }

    m_nodeName = nodeName;
    m_groupAddress = multicastGroup;
    m_port = port;

    return true;
}

void ShNetDebugger::outStatistic(int outBytes)
{
    m_outPackets++;
    m_outBytes += outBytes;
    m_qml->rootObject()->setProperty("outPackets", m_outPackets);
    m_qml->rootObject()->setProperty("outBytes", m_outBytes);
}
